
#ifndef PARTICLE_HPP
#define PARTICLE_HPP

#include <vw/Math/Vector.h>
#include <opencv/cv.h>
#include <GL/gl.h>

#include <list>
#include <math.h>

typedef vw::Vector <float, 2> Vec;
typedef vw::Vector <float, 3> State;

float randf ();
float randp ();

template <int N>
vw::Vector <float, N> randv (float _c, float _amp = 1.0f)
{
	vw::Vector <float, N> v;

	for (int i = 0; i < N; ++i)
	{
		v [i] = _amp * randp () - 0.5 + _c;
	}

	return v;
}


class System;

class Point
{
public:
	Vec s;
	State color;
public:
	virtual void draw ()
	{

		glColor4f (pow (color [0], 0.3f), pow (color [1], 0.3f), pow (color [2], 0.3f), 1.0f);
		glVertex3f (s [0], s [1], 0);
	}
};

class Particle : public Point
{
protected:
	std::list <Particle *>::iterator iterator;
	std::list <Point> tail;
	System *ps;
public:	
	Vec v;
	Vec a;
	State state;
	bool alive;

public:
	Particle (System *_ps) : ps (_ps), alive (true)
	{
		tail.push_back (*this);
	}

	// update physical parameters
	virtual void move (float _dt)
	{
		tail.push_back (Point (*this));

		while (tail.size () > 5) {
			tail.pop_front ();
		}

		if (s [0] < -1.0f) s [0] += 2.0f;
		if (s [1] < -1.0f) s [1] += 2.0f;
		if (s [0] > 1.0f) s [0] -= 2.0f;
		if (s [1] > 1.0f) s [1] -= 2.0f;
	}

	virtual void update (float _dt) = 0;
	
	virtual void draw ()
	{
		glBegin (GL_POINTS);
		
		for (std::list <Point>::iterator pos = tail.begin ();
				pos != tail.end (); ++pos)
		{
			pos->draw ();
		}

		glEnd ();
	}

};

class ZeroParticle : public Particle
{
public:
	void update (float _dt)
	{
	}

	ZeroParticle (System *_ps) : Particle (_ps)
	{
		s = vw::Vector <float, 2> (0, 0);
		v = vw::Vector <float, 2> (0, 0);
		a = vw::Vector <float, 2> (0, 0);
		color = vw::Vector <float, 3> (randp (), randp (), randp ());
		state = State (0, 0, 0);
	}
};


class System
{
public:
	typedef std::list <Particle *>::iterator iterator;
	std::list <Particle *> ps;

	vw::Vector <float, 2> center;

public:
	void clear ()
	{
		while (!ps.empty ())
		{
			delete ps.back ();
			ps.pop_back ();
		}
	}

	~System ()
	{
		clear ();
	}

	template <typename T>
	T *emit (int _n)
	{
		T *p;
		for (int i = 0; i < _n; ++i) {
			p = new T (this);
			ps.push_back (p);
		}

		return p;
	}

	void update_all (float _dt)
	{
		center [0] = 0.0f;
		center [1] = 0.0f;

		for (iterator pos = ps.begin ();
				pos != ps.end ();) {
			center += (*pos)->s;

			iterator del = pos;
			++pos;

			if (!(*del)->alive)
				ps.erase (del);
		}	

		for (iterator pos = ps.begin ();
				pos != ps.end (); ++pos) {
			(*pos)->update (_dt);
			(*pos)->move (_dt);
		}
	}

	void draw_all ()
	{
		for (iterator pos = ps.begin ();
				pos != ps.end (); ++pos) {
			(*pos)->draw ();
		}
	}

	iterator begin () 
	{
		return ps.begin ();
	}

	iterator end ()
	{
		return ps.end ();
	}

};

class SwarmParticle : public Particle
{
public:
	void update (float _dt)
	{
		vw::Vector <float, 2> ave (0, 0);

		for (std::list <Particle *>::iterator pos = ps->begin (); 
				pos != ps->end (); ++pos) {
			Particle *p = *pos;

			if (p == this) continue;

			if (vw::math::norm_2 (p->color - color) < 0.4)
				ave += (p->s - s);
			
			float dist = vw::math::norm_2 (p->s - s);

			if (dist < 0.05)
				v -= 0.7f * (p->s - s);

			if (dist < 0.2 && p->state [0] > 0.5f) {
				v -= 0.8f * (p->s - s);

				if (randp () > 0.9) {
					//SwarmParticle *child = ps->emit <SwarmParticle> (1);
					//child->s = s;
					//child->color = color;
				}
			}

			if (dist < 0.05 && p->state [0] > 0.5f)
				alive = false;
		}

		a = 0.9f * ave;
		s += v * _dt;
		v += a * _dt - 0.01 * v;
	}

	SwarmParticle (System *_ps) : Particle (_ps)
	{
		s = vw::Vector <float, 2> (randf (), randf ());
		v = vw::Vector <float, 2> (randf (), randf ());
		a = vw::Vector <float, 2> (0, 0);
		color = vw::Vector <float, 3> (randf (), randf (), randf ());
	}
};

class SharkParticle : public Particle
{
public:
	void update (float _dt)
	{
		vw::Vector <float, 2> ave (0, 0);

		for (std::list <Particle *>::iterator pos = ps->begin (); 
				pos != ps->end (); ++pos) {
			Particle *p = *pos;

			if (p == this) continue;

			if (vw::math::norm_2 (p->s - s) < 0.2)
				v += 0.9f * (p->s - s);
			
			ave += 0.2f * (p->s - s);
		}

		v += 0.01 * ave - 0.1 * v;
		s += v * _dt;
	}

	virtual void move (float _dt)
	{
		tail.push_back (Point (*this));

		while (tail.size () > 10) {
			tail.pop_front ();
		}
	}

	SharkParticle (System *_ps) : Particle (_ps)
	{
		s = vw::Vector <float, 2> (randf (), randf ());
		v = vw::Vector <float, 2> (randf (), randf ());
		a = vw::Vector <float, 2> (0, 0);
		color = vw::Vector <float, 3> (1.0, 0.0, 0.0);
		state [0] = 1.0f;
	}
};


#endif
